package com.weimin.client;

import cn.hutool.json.JSONUtil;
import com.weimin.model.MessageModel;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class NatClient {
    /**
     * 协调服务器IP
     */
    private static final String COORDINATOR_IP = "127.0.0.1";
//    private static final String COORDINATOR_IP = "120.79.87.233";
    /**
     * 协调服务器端口
     */
    private static final int COORDINATOR_PORT = 15321;
    /**
     * 本地随机端口
     */
    private static final int LOCAL_PORT = 3000 + (int) (Math.random() * 1000);
    /**
     * udp 服务端
     */
    private DatagramSocket udpSocket;
    /**
     * 客户端编号
     */
    private String clientId;
    /**
     * 对等方的 公网IP
     */
    private InetAddress peerIp;
    /**
     * 对等方的 公网端口
     */
    private int peerPort;
    /**
     * 对等方的客户端编号
     */
    private String peerId;
    /**
     * 最大UDP包大小
     */
    private int maxByteLen = 1024;
    private boolean isRunning = true;

    public NatClient(String clientId) throws Exception {
        this.clientId = clientId;
        // 创建UDP套接字并绑定本地端口
        this.udpSocket = new DatagramSocket(LOCAL_PORT);
        System.out.println("客户端 " + clientId + " 已启动，本地端口: " + LOCAL_PORT);
    }

    @SuppressWarnings("AlibabaAvoidManuallyCreateThread")
    public void start() throws IOException {
        // 启动消息接收线程
        new Thread(this::receiveMessages).start();
        // 向协调服务器注册
        registerWithCoordinator();
        // 命令输入处理
        handleUserInput();
    }

    /**
     * 向协调服务器注册
     */
    private void registerWithCoordinator() throws IOException {
        MessageModel messageModel = new MessageModel();
        messageModel.setType("REGISTER");
        messageModel.setClientId(this.clientId);
        sendToCoordinator(JSONUtil.toJsonStr(messageModel));
    }

    /**
     * 向协调服务器发送协调命令获取对等方信息
     *
     * @param targetId 对等方客户端编号
     */
    private void pairWithCoordinator(String targetId) throws IOException {
        MessageModel messageModel = new MessageModel();
        messageModel.setType("PAIR");
        messageModel.setClientId(this.clientId);
        messageModel.setTargetId(targetId);
        sendToCoordinator(JSONUtil.toJsonStr(messageModel));
    }

    /**
     * 发送消息到协调服务器
     *
     * @param message 发送消息到协调服务器
     */
    private void sendToCoordinator(String message) throws IOException {
        byte[] sendData = message.getBytes();
        InetAddress coordinatorAddr = InetAddress.getByName(COORDINATOR_IP);
        DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, coordinatorAddr, COORDINATOR_PORT);
        udpSocket.send(sendPacket);
    }

    /**
     * 发送消息到对等方
     *
     * @param message 发送的消息
     */
    private void sendToPeer(String message) throws IOException {
        if (peerIp == null || peerPort == 0) {
            System.out.println("未配对，请先使用 pair 命令配对");
            return;
        }

        byte[] sendData = message.getBytes();
        DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, peerIp, peerPort);
        udpSocket.send(sendPacket);
        System.out.println("已发送到 " + peerId + ": " + message);
    }

    /**
     * 接收消息线程
     */
    private void receiveMessages() {
        byte[] receiveData = new byte[maxByteLen];
        while (isRunning) {
            try {
                DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
                udpSocket.receive(receivePacket);
                String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
                InetAddress senderIp = receivePacket.getAddress();
                int senderPort = receivePacket.getPort();
                // 判断是来自协调服务器还是对等方
                if (senderIp.getHostAddress().equals(COORDINATOR_IP) && senderPort == COORDINATOR_PORT) {
                    // 处理来自协调服务器的消息
                    handleCoordinatorMessage(message);
                } else if (senderIp.equals(peerIp) && senderPort == peerPort) {
                    handlePeerMessage(message);
                } else if (peerIp == null) {
                    System.out.println("\n收到未知来源 (" + senderIp + ":" + senderPort + ") 的消息: " + message);
                    printInputTip();
                }
            } catch (IOException e) {
                if (!isRunning) {
                    break;
                }
                System.err.println("接收消息错误: " + e.getMessage());
            }
        }
    }

    private void printInputTip() {
        System.out.print("\n(pair [id])(send [msg])> ");
    }

    /**
     * 处理来自对等方的消息
     *
     * @param message 消息
     */
    private void handlePeerMessage(String message) {
        System.out.println("\n收到来自对等方消息(" + peerId + "): " + message);
        printInputTip();
    }

    /**
     * 处理来自协调服务器的消息
     *
     * @param message 消息
     */
    private void handleCoordinatorMessage(String message) {
        if (!JSONUtil.isTypeJSON(message)) {
            System.out.println("\n异常消息: " + message);
            printInputTip();
            return;
        }
        MessageModel messageModel = JSONUtil.toBean(message, MessageModel.class);
        if (messageModel == null || messageModel.getType() == null) {
            System.out.println("\n异常空类型消息: " + message);
            printInputTip();
            return;
        }
        switch (messageModel.getType()) {
            case "PAIR_OK":
                try {
                    this.peerId = messageModel.getTargetId();
                    this.peerIp = InetAddress.getByName(messageModel.getTargetIp());
                    this.peerPort = messageModel.getTargetPort();
                    System.out.println("\n配对成功! 对方ID: " + peerId + ", 地址: " + peerIp + ":" + peerPort);
                    System.out.println("开始UDP打洞...");
                    // 发送打洞数据包
                    for (int i = 0; i < 10; i++) {
                        sendToPeer("HELLO_FROM " + clientId + " (" + (i + 1) + "/3)");
                        Thread.sleep(500);
                    }
                    System.out.println("打洞完成，现在可以发送消息了");
                    System.out.print("> ");
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
            default:
                System.out.println("\n错误类型消息: " + message);
                break;
        }
        printInputTip();
    }

    private void handleUserInput() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (isRunning) {
            printInputTip();
            String input = scanner.nextLine().trim();
            if (input.startsWith("pair ")) {
                String[] parts = input.split(" ", 2);
                if (parts.length == 2) {
                    String targetId = parts[1];
                    pairWithCoordinator(targetId);
                    System.out.println("已发送配对请求到 " + targetId);
                } else {
                    System.out.println("格式错误: pair <目标客户端ID>");
                }
            } else if (input.startsWith("send ")) {
                String[] parts = input.split(" ", 2);
                if (parts.length == 2) {
                    sendToPeer(parts[1]);
                } else {
                    System.out.println("格式错误: send <消息内容>");
                }
            } else if ("exit".equals(input)) {
                isRunning = false;
                System.out.println("退出程序...");
                udpSocket.close();
                scanner.close();
            } else {
                System.out.println("未知命令，请使用: pair, send, exit");
            }
        }
    }
}
